{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Visualizing Convolutional Neural Networks and Neural Style Transfer\n",
    "\n",
    "July 2019 <br>\n",
    "**Author:** Matthew Stewart"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<style>\n",
       "blockquote { background: #AEDE94; }\n",
       "h1 { \n",
       "    padding-top: 25px;\n",
       "    padding-bottom: 25px;\n",
       "    text-align: left; \n",
       "    padding-left: 10px;\n",
       "    background-color: #DDDDDD; \n",
       "    color: black;\n",
       "}\n",
       "h2 { \n",
       "    padding-top: 10px;\n",
       "    padding-bottom: 10px;\n",
       "    text-align: left; \n",
       "    padding-left: 5px;\n",
       "    background-color: #EEEEEE; \n",
       "    color: black;\n",
       "}\n",
       "\n",
       "div.exercise {\n",
       "\tbackground-color: #ffcccc;\n",
       "\tborder-color: #E9967A; \t\n",
       "\tborder-left: 5px solid #800080; \n",
       "\tpadding: 0.5em;\n",
       "}\n",
       "div.discussion {\n",
       "\tbackground-color: #ccffcc;\n",
       "\tborder-color: #88E97A;\n",
       "\tborder-left: 5px solid #0A8000; \n",
       "\tpadding: 0.5em;\n",
       "}\n",
       "div.theme {\n",
       "\tbackground-color: #DDDDDD;\n",
       "\tborder-color: #E9967A; \t\n",
       "\tborder-left: 5px solid #800080; \n",
       "\tpadding: 0.5em;\n",
       "\tfont-size: 18pt;\n",
       "}\n",
       "div.gc { \n",
       "\tbackground-color: #AEDE94;\n",
       "\tborder-color: #E9967A; \t \n",
       "\tborder-left: 5px solid #800080; \n",
       "\tpadding: 0.5em;\n",
       "\tfont-size: 12pt;\n",
       "}\n",
       "p.q1 { \n",
       "    padding-top: 5px;\n",
       "    padding-bottom: 5px;\n",
       "    text-align: left; \n",
       "    padding-left: 5px;\n",
       "    background-color: #EEEEEE; \n",
       "    color: black;\n",
       "}\n",
       "header {\n",
       "   padding-top: 35px;\n",
       "    padding-bottom: 35px;\n",
       "    text-align: left; \n",
       "    padding-left: 10px;\n",
       "    background-color: #DDDDDD; \n",
       "    color: black;\n",
       "}\n",
       "</style>\n",
       "\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#RUN THIS CELL \n",
    "import requests\n",
    "from IPython.core.display import HTML\n",
    "styles = requests.get(\"https://raw.githubusercontent.com/Harvard-IACS/2019-CS109B/master/content/styles/cs109.css\").text\n",
    "HTML(styles)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "import numpy as np\n",
    "\n",
    "from keras import backend as K\n",
    "from keras.applications import vgg16, vgg19\n",
    "from keras.preprocessing.image import load_img\n",
    "\n",
    "from scipy.misc import imsave\n",
    "from scipy.optimize import fmin_l_bfgs_b\n",
    "\n",
    "# preprocessing\n",
    "from utils import preprocess_image, deprocess_image\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 1: Content loss\n",
    "\n",
    "We can generate an image that combines the content and style of a pair with a loss function that incorporates this information. This is achieved with two terms, one that mimics the specific ativations of a certain layer for the content image, and a second term that mimics the style. The variable to optimize in the loss function will be a generated image that aims to minimize the proposed cost. Note that to optimize this function, we will perform gradient descent __on the pixel values__, rather than on the neural network weights.\n",
    "\n",
    "We will load a trained neural network called VGG-16 proposed in [1](https://arxiv.org/pdf/1409.1556.pdf), who secured the first and second place in the localisation and classification tracks of ImageNet Challenge in 2014, respectively. This network has been trained to discriminate over 1000 classes over more than a million images. We will use the activation values obtained for an image of interest to represent the content and styles. In order to do so, we will feed-forward the image of interest and observe it's activation values at the indicated layer.\n",
    "\n",
    "The content loss function measures how much the feature map of the generated image differs from the feature map of the source image. We will only consider a single layer to represent the contents of an image. The authors of this technique indicated they obtained better results when doing so. We denote the feature maps for layer $l$ with $a^{[l]} \\in \\mathbb{R}^{n_H^{[l]} \\times n_W^{[l]} \\times n_C^{[l]}}$. Parameter $n_C^{[l]}$ is the number of filters/channels in layer $l$, $n_H^{[l]}$ and $n_W^{[l]}$ are the height and width.\n",
    "\n",
    "The content loss is then given by:\n",
    "\\begin{equation}\n",
    "    J^{[l]}_C = \\big\\Vert a^{[l](G)} - a^{[l](C)} \\big\\Vert^2_{\\mathcal{F}},\n",
    "\\end{equation}\n",
    "where $a^{[l](G)}$ refers to the layer's activation values of the generated image, and $a^{[l](C)}$ to those of the content image."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"exercise\"> <b> Part 1: Content loss</b> </div>\n",
    "    \n",
    "Implement funtion `feature_reconstruction_loss` that computes the loss of two feature inputs. You will need to use [keras backend functions](https://keras.io/backend/#backend-functions) to complete the exercise."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def feature_reconstruction_loss(base, output):\n",
    "    \"\"\"\n",
    "    Compute the content loss for style transfer.\n",
    "    \n",
    "    Inputs:\n",
    "    - output: features of the generated image, Tensor with shape [height, width, channels]\n",
    "    - base: features of the content image, Tensor with shape [height, width, channels]\n",
    "    \n",
    "    Returns:\n",
    "    - scalar content loss\n",
    "    \"\"\"\n",
    "    # YOUR CODE GOES HERE    \n",
    "    return K.sum(K.square(output - base))    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Test your implementation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "np.random.seed(1)\n",
    "base = np.random.randn(10,10,3)\n",
    "output = np.random.randn(10,10,3)\n",
    "a = K.constant(base)\n",
    "b = K.constant(output)\n",
    "test = feature_reconstruction_loss(a, b)\n",
    "print('Result:          ', K.eval(test))\n",
    "print('Expected result: ', 605.62195)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 2: Style loss\n",
    "\n",
    "The style measures the similarity among filters in a set of layers. In order to compute that similarity, we will compute the Gram matrix of the activation values for the style layers, i.e., $a^{[l]}$ for some set $\\mathcal{L}$. The Gram matrix is related to the empirical covariance matrix, and therefore, reflects the statistics of the activation values.\n",
    "\n",
    "Given a feature map $a^{[l]}$ of shape $(n_H^{[l]}, n_W^{[l]}, n_C^{[l]})$, the Gram matrix has shape $(n_C^{[l]}, n_C^{[l]})$ and its elements are given by:\n",
    "\\begin{equation*}\n",
    "    G^{[l]}_{k k'} = \\sum_{i=1}^{n_H^{[l]}} \\sum_{j=1}^{n_W^{[l]}} a^{[l]}_{ijk} a^{[l]}_{ijk'}.\n",
    "\\end{equation*}\n",
    "The output is a 2-D matrix which approximately measures the cross-correlation among different filters for a given layer. This in essence constitutes the style of a layer."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"exercise\"> <b> Part 2: Computing the Gram matrix</b> </div>\n",
    "\n",
    "We implement a function that computes the Gram matrix of a given keras tensor. This can be accomplished efficiently if $x$ is reshaped as a tensor of shape ($n_C^{[l]} \\times n_H^{[l]} n_W^{[l]}$) and then you compute the outer product of this matrix with itself. We need to use [keras backend functions](https://keras.io/backend/#backend-functions) for this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def gram_matrix(x):\n",
    "    \"\"\"\n",
    "    Computes the outer-product of the input tensor x.\n",
    "\n",
    "    Input:\n",
    "    - x: input tensor of shape (H, W, C)\n",
    "\n",
    "    Returns:\n",
    "    - tensor of shape (C, C) corresponding to the Gram matrix of\n",
    "    the input image.\n",
    "    \"\"\"\n",
    "    # YOUR CODE GOES HERE   \n",
    "    features = K.batch_flatten(K.permute_dimensions(x, (2, 0, 1)))\n",
    "    return K.dot(features, K.transpose(features))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Test your implementation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "np.random.seed(1)\n",
    "x_np = np.random.randn(10,10,3)\n",
    "x = K.constant(x_np)\n",
    "test = gram_matrix(x)\n",
    "print('Result:\\n', K.eval(test))\n",
    "print('Expected:\\n', np.array([[99.75723, -9.96186, -1.4740534], [-9.96186, 86.854324, -4.141108 ], [-1.4740534, -4.141108, 82.30106  ]]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 3: Style loss: layer's loss\n",
    "\n",
    "Now we can tackle the style loss. For a given layer $l$, the style loss is defined as follows:\n",
    "\\begin{equation*}\n",
    "    J^{[l]}_S = \\frac{1}{4 (n^{[l]}_W n^{[l]}_H)^2} \\Big\\Vert G^{[l](S)} - G^{[l](G)}\\Big\\Vert^2_{\\mathcal{F}}.\n",
    "\\end{equation*}\n",
    "\n",
    "In practice we compute the style loss at a set of layers $\\mathcal{L}$ rather than just a single layer $l$; then the total style loss is the sum of style losses at each layer:\n",
    "\n",
    "$$J_S = \\sum_{l \\in \\mathcal{L}} \\lambda_l J^{[l]}_S$$\n",
    "where $\\lambda_l$ corresponds to a weighting parameter."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"exercise\"> <b> Part 3: Computing the layer's loss</b> </div>\n",
    "\n",
    "Implement `style_reconstruction_loss` that computes the loss for a given layer $l$. We again need to use [keras backend functions](https://keras.io/backend/#backend-functions) for this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def style_reconstruction_loss(base, output):\n",
    "    \"\"\"\n",
    "    Computes the style reconstruction loss. It encourages the output img \n",
    "    to have same stylistic features as style image.\n",
    "    \n",
    "    Inputs:\n",
    "    - base: features at given layer of the style image.\n",
    "    - output: features of the same length as base of the generated image.\n",
    "      \n",
    "    Returns:\n",
    "    - style_loss: scalar style loss\n",
    "    \"\"\"\n",
    "    # YOUR CODE GOES HERE \n",
    "    H, W = int(base.shape[0]), int(base.shape[1])\n",
    "    gram_base = gram_matrix(base)\n",
    "    gram_output = gram_matrix(output)\n",
    "    factor = 1.0 / float((2*H*W)**2)\n",
    "    out = factor * K.sum(K.square(gram_output - gram_base))\n",
    "    return out"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Test your implementation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "np.random.seed(1)\n",
    "x = np.random.randn(10,10,3)\n",
    "y = np.random.randn(10,10,3)\n",
    "a = K.constant(x)\n",
    "b = K.constant(y)\n",
    "test = style_reconstruction_loss(a, b)\n",
    "print('Result:  ', K.eval(test))\n",
    "print('Expected:', 0.09799164)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 4: Total-variation regularization\n",
    "We will also encourage smoothness in the image using a total-variation regularizer. This penalty term will reduce variation among the neighboring pixel values.\n",
    "\n",
    "The following expression constitues the regularization penalty over all pairs that are next to each other horizontally or vertically. The expression is independent among different RGB channels.\n",
    "\\begin{equation*}\n",
    "    J_{tv} = \\sum_{c=1}^3\\sum_{i=1}^{n^{[l]}_H-1} \\sum_{j=1}^{n^{[l]}_W-1} \\left( (x_{i,j+1, c} - x_{i,j,c})^2 + (x_{i+1, j,c} - x_{i,j,c})^2  \\right)\n",
    "\\end{equation*}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"exercise\"> <b> Part 4: Total-variation regularization</b> </div>\n",
    "\n",
    "In the next cell, fill in the definition for the TV loss term.\n",
    "\n",
    "__Remark:__ $x$ has dimension $(1, n_H^{[l]}, n_W^{[l]}, n_C^{[l]})$, which is different from the 3D-tensors we used before."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def total_variation_loss(x):\n",
    "    \"\"\"\n",
    "    Total variational loss. Encourages spatial smoothness \n",
    "    in the output image.\n",
    "    \n",
    "    Inputs:\n",
    "    - x: image with pixels, has shape 1 x H x W x C.\n",
    "      \n",
    "    Returns:\n",
    "    - total variation loss, a scalar number.\n",
    "    \"\"\"\n",
    "    # YOUR CODE GOES HERE \n",
    "    a = K.square(x[:, :-1, :-1, :] - x[:, 1:, :-1, :])\n",
    "    b = K.square(x[:, :-1, :-1, :] - x[:, :-1, 1:, :])\n",
    "    return K.sum(a + b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Test your implementation. If you do not get exact results but similar, you may still have a correct implementation. The goal is that you write a smoother for neighboring pixels."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "np.random.seed(1)\n",
    "x_np = np.random.randn(1,10,10,3)\n",
    "x = K.constant(x_np)\n",
    "test = total_variation_loss(x)\n",
    "print('Result:  ', K.eval(test))\n",
    "print('Expected:', 937.0538)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Part 5: Style transfer\n",
    "\n",
    "We now put it all together and generate some images! The `style_transfer` function below combines all the losses you coded up above and optimizes for an image that minimizes the total loss. Read the code and comments to understand the procedure."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def style_transfer(base_img_path, style_img_path, output_img_path, convnet='vgg16', \n",
    "        content_weight=3e-2, style_weights=(20000, 500, 12, 1, 1), tv_weight=5e-2, content_layer='block4_conv2', \n",
    "        style_layers=['block1_conv1', 'block2_conv1', 'block3_conv1', 'block4_conv1', 'block5_conv1'], iterations=50):\n",
    "    \n",
    "    print('\\nInitializing Neural Style model...')\n",
    "\n",
    "    # Determine the image sizes. Fix the output size from the content image.\n",
    "    print('\\n\\tResizing images...')\n",
    "    width, height = load_img(base_img_path).size\n",
    "    new_dims = (height, width)\n",
    "\n",
    "    # Preprocess content and style images. Resizes the style image if needed.\n",
    "    content_img = K.variable(preprocess_image(base_img_path, new_dims))\n",
    "    style_img = K.variable(preprocess_image(style_img_path, new_dims))\n",
    "\n",
    "    # Create an output placeholder with desired shape.\n",
    "    # It will correspond to the generated image after minimizing the loss function.\n",
    "    output_img = K.placeholder((1, height, width, 3))\n",
    "    \n",
    "    # Sanity check on dimensions\n",
    "    print(\"\\tSize of content image is: {}\".format(K.int_shape(content_img)))\n",
    "    print(\"\\tSize of style image is: {}\".format(K.int_shape(style_img)))\n",
    "    print(\"\\tSize of output image is: {}\".format(K.int_shape(output_img)))\n",
    "\n",
    "    # Combine the 3 images into a single Keras tensor, for ease of manipulation\n",
    "    # The first dimension of a tensor identifies the example/input.\n",
    "    input_img = K.concatenate([content_img, style_img, output_img], axis=0)\n",
    "\n",
    "    # Initialize the vgg16 model\n",
    "    print('\\tLoading {} model'.format(convnet.upper()))\n",
    "\n",
    "    if convnet == 'vgg16':\n",
    "        model = vgg16.VGG16(input_tensor=input_img, weights='imagenet', include_top=False)\n",
    "    else:\n",
    "        model = vgg19.VGG19(input_tensor=input_img, weights='imagenet', include_top=False)\n",
    "        \n",
    "    print('\\tComputing losses...')\n",
    "    # Get the symbolic outputs of each \"key\" layer (they have unique names).\n",
    "    # The dictionary outputs an evaluation when the model is fed an input.\n",
    "    outputs_dict = dict([(layer.name, layer.output) for layer in model.layers])\n",
    "\n",
    "    # Extract features from the content layer\n",
    "    content_features = outputs_dict[content_layer]\n",
    "\n",
    "    # Extract the activations of the base image and the output image\n",
    "    base_image_features = content_features[0, :, :, :]  # 0 corresponds to base\n",
    "    combination_features = content_features[2, :, :, :] # 2 coresponds to output\n",
    "\n",
    "    # Calculate the feature reconstruction loss\n",
    "    content_loss = content_weight * feature_reconstruction_loss(base_image_features, combination_features)\n",
    "\n",
    "    # For each style layer compute style loss\n",
    "    # The total style loss is the weighted sum of those losses\n",
    "    temp_style_loss = K.variable(0.0)       # we update this variable in the loop\n",
    "    weight = 1.0 / float(len(style_layers))\n",
    "    \n",
    "    for i, layer in enumerate(style_layers):\n",
    "        # extract features of given layer\n",
    "        style_features = outputs_dict[layer]\n",
    "        # from those features, extract style and output activations\n",
    "        style_image_features = style_features[1, :, :, :]   # 1 corresponds to style image\n",
    "        output_style_features = style_features[2, :, :, :]  # 2 coresponds to generated image\n",
    "        temp_style_loss += style_weights[i] * weight * \\\n",
    "                    style_reconstruction_loss(style_image_features, output_style_features)\n",
    "    style_loss = temp_style_loss\n",
    "\n",
    "    # Compute total variational loss.\n",
    "    tv_loss = tv_weight * total_variation_loss(output_img)\n",
    "\n",
    "    # Composite loss\n",
    "    total_loss = content_loss + style_loss + tv_loss\n",
    "    \n",
    "    # Compute gradients of output img with respect to total_loss\n",
    "    print('\\tComputing gradients...')\n",
    "    grads = K.gradients(total_loss, output_img)\n",
    "    \n",
    "    outputs = [total_loss] + grads\n",
    "    loss_and_grads = K.function([output_img], outputs)  \n",
    "    \n",
    "    # Initialize the generated image from random noise\n",
    "    x = np.random.uniform(0, 255, (1, height, width, 3)) - 128.\n",
    "    \n",
    "    # Loss function that takes a vectorized input image, for the solver\n",
    "    def loss(x):\n",
    "        x = x.reshape((1, height, width, 3))   # reshape\n",
    "        return loss_and_grads([x])[0]\n",
    "    \n",
    "    # Gradient function that takes a vectorized input image, for the solver\n",
    "    def grads(x):\n",
    "        x = x.reshape((1, height, width, 3))   # reshape\n",
    "        return loss_and_grads([x])[1].flatten().astype('float64')\n",
    "    \n",
    "    # Fit over the total iterations\n",
    "    for i in range(iterations+1):\n",
    "        print('\\n\\tIteration: {}'.format(i+1))\n",
    "\n",
    "        toc = time.time()\n",
    "        x, min_val, info = fmin_l_bfgs_b(loss, x.flatten(), fprime=grads, maxfun=20)\n",
    "\n",
    "        # save current generated image\n",
    "        if i%10 == 0:\n",
    "            img = deprocess_image(x.copy(), height, width)\n",
    "            fname = output_img_path + '_at_iteration_%d.png' % (i)\n",
    "            imsave(fname, img)\n",
    "            print('\\t\\tImage saved as', fname)\n",
    "\n",
    "        tic = time.time()\n",
    "\n",
    "        print('\\t\\tLoss: {:.2e}, Time: {} seconds'.format(float(min_val), float(tic-toc)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"exercise\"> <b> Part 5: Generate pictures!</b> </div>\n",
    "\n",
    "Find style and content images under `images/inputs/`.\n",
    "\n",
    "* The `base_img_path` is the filename of content image.\n",
    "* The `style_img_path` is the filename of style image.\n",
    "* The `output_img_path` is the filename of generated image.\n",
    "* The `convnet` is for the neural network weights, VGG-16 or VGG-19.\n",
    "* The `content_layer` specifies which layer to use for content loss.\n",
    "* The `content_weight` weights the content loss in the overall composite loss function. Increasing the value of this parameter will make the final image look more realistic (closer to the original content).\n",
    "* `style_layers` specifies a list of which layers to use for the style loss. \n",
    "* `style_weights` specifies a list of weights to use for each layer in style_layers (each of which will contribute a term to the overall style loss). We generally use higher weights for the earlier style layers because they describe more local/smaller scale features, which are more important to texture than features over larger receptive fields. In general, increasing these weights will make the resulting image look less like the original content and more distorted towards the appearance of the style image.\n",
    "* `tv_weight` specifies the weighting of total variation regularization in the overall loss function. Increasing this value makes the resulting image look smoother and less jagged, at the cost of lower fidelity to style and content. \n",
    "\n",
    "**CAUTION:** The script saves an image every 10 iterations."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Great wave of Kanagawa + Chicago"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "params = {\n",
    "'base_img_path' : 'images/inputs/chicago.jpg', \n",
    "'style_img_path' : 'images/inputs/great_wave_of_kanagawa.jpg', \n",
    "'output_img_path' : 'images/results/wave_chicago', \n",
    "'convnet' : 'vgg16', \n",
    "'content_weight' : 500, \n",
    "'style_weights' : (10, 10, 50, 10, 10),\n",
    "'tv_weight' : 200, \n",
    "'content_layer' : 'block4_conv2', \n",
    "'style_layers' : ['block1_conv1',\n",
    "                  'block2_conv1',\n",
    "                  'block3_conv1', \n",
    "                  'block4_conv1', \n",
    "                  'block5_conv1'], \n",
    "'iterations' : 50\n",
    "}\n",
    "\n",
    "style_transfer(**params)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Starry night + Tübingen"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "params = {\n",
    "'base_img_path' : 'images/inputs/tubingen.jpg', \n",
    "'style_img_path' : 'images/inputs/starry_night.jpg', \n",
    "'output_img_path' : 'images/results/starry_tubingen', \n",
    "'convnet' : 'vgg16', \n",
    "'content_weight' : 100, \n",
    "'style_weights' : (1000, 100, 12, 1, 1),\n",
    "'tv_weight' : 200, \n",
    "'content_layer' : 'block4_conv2', \n",
    "'style_layers' : ['block1_conv1',\n",
    "                  'block2_conv1',\n",
    "                  'block3_conv1', \n",
    "                  'block4_conv1', \n",
    "                  'block5_conv1'], \n",
    "'iterations' : 50\n",
    "}\n",
    "\n",
    "style_transfer(**params)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Acknowledgments\n",
    "\n",
    "- The implementation uses code from Francois Chollet's neural style transfer.\n",
    "- The implementation uses code from Kevin Zakka's neural style transfer, under MIT license.\n",
    "- The hierarchy borrows from Giuseppe Bonaccorso's gist, under MIT license."
   ]
  }
 ],
 "metadata": {
  "@webio": {
   "lastCommId": "ff658b2f3aa64ee9865694eb29ec69b9",
   "lastKernelId": "6e21dca0-bb7a-40ff-bdc7-68921243151c"
  },
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
